﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;

namespace OverStore.Runtime.Storage
{
    /// <summary>
    /// Convert <see cref="IDataReader"/> to sequense of <see cref="IRecord"/>.
    /// </summary>
    public sealed class ReaderEnumerator : TypedRecord, IEnumerator<TypedRecord>
    {
        private readonly Func<DataCommandContainer> _ExecuteReaderFunc;
        private DataCommandContainer _DataReaderContainer;
        private Dictionary<string, int> _FieldIndex;
        private bool _Disposed;
        private IDataReader _DataReader;

        /// <summary>
        /// Initializes a new instance of the <see cref="ReaderEnumerator"/> class.
        /// </summary>
        /// <param name="executeReaderFunc">The create command func.</param>
        public ReaderEnumerator(Func<DataCommandContainer> executeReaderFunc)
        {
            Debug.Assert(executeReaderFunc != null, "executeReaderFunc != null");
            _ExecuteReaderFunc = executeReaderFunc;
        }

        /// <summary>
        /// Gets the element in the collection at the current position of the enumerator.
        /// </summary>
        /// <returns>
        /// The element in the collection at the current position of the enumerator.
        /// </returns>
        public TypedRecord Current
        {
            get
            {
                if (_Disposed)
                    throw new ObjectDisposedException("ReaderEnumerator");
                return this;
            }
        }

        /// <summary>
        /// Gets the element in the collection at the current position of the enumerator.
        /// </summary>
        /// <returns>
        /// The element in the collection at the current position of the enumerator.
        /// </returns>
        object IEnumerator.Current
        {
            get
            {
                return Current;
            }
        }

        private IDataReader DataReader
        {
            get
            {
                if (_DataReaderContainer == null)
                {
                    _DataReaderContainer = _ExecuteReaderFunc();
                    _DataReader = _DataReaderContainer.DataReader;
                }
                return _DataReader;
            }
        }

        private IDictionary<string, int> FieldIndex
        {
            get
            {
                if (_Disposed)
                    throw new ObjectDisposedException("ReaderEnumerator");
                if (_FieldIndex == null)
                {
                    _FieldIndex = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
                    for (int i = 0; i < DataReader.FieldCount; i++)
                        _FieldIndex.Add(DataReader.GetName(i), i);
                }

                return _FieldIndex;
            }
        }

        /// <summary>
        /// Advances the enumerator to the next element of the collection.
        /// </summary>
        /// <returns>
        /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
        /// </returns>
        /// <exception cref="T:System.InvalidOperationException">
        /// The collection was modified after the enumerator was created.
        /// </exception>
        public bool MoveNext()
        {
            if (_Disposed)
                throw new ObjectDisposedException("ReaderEnumerator");
            return DataReader.Read();
        }

        /// <summary>
        /// Sets the enumerator to its initial position, which is before the first element in the collection.
        /// </summary>
        /// <exception cref="T:System.InvalidOperationException">
        /// The collection was modified after the enumerator was created.
        /// </exception>
        public void Reset()
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            if (!_Disposed)
            {
                if (_DataReaderContainer == null)
                    return;
                _DataReaderContainer.Dispose();
                _Disposed = true;
            }
        }

        /// <summary>
        /// Try get result with specified name. If result exisits, method returns true,
        /// otherwise false.
        /// Operation should not checks arguments for null.
        /// </summary>
        /// <param name="name">The case-insensitive result name.</param>
        /// <param name="result">The result with specified name.</param>
        /// <returns>
        /// True if result with such name exists, false otherwise.
        /// </returns>
        public override bool TryGetValue(string name, out object result)
        {
            int index;
            if (!FieldIndex.TryGetValue(name, out index))
            {
                result = null;
                return false;
            }

            result = DataReader.GetValue(index);
            if (result == DBNull.Value)
                result = null;
            return true;
        }

        #region TypedRecord Members

        /// <summary>
        /// Gets the boolean result with specified name.
        /// </summary>
        public override bool GetBoolean(string name)
        {
            return DataReader.GetBoolean(GetOrdinal(name));
        }

        /// <summary>
        /// Gets the nullable boolean result with specified name.
        /// </summary>
        public override bool? GetNullableBoolean(string name)
        {
            var i = GetOrdinal(name);
            if (DataReader.IsDBNull(i))
                return new bool?();
            else
                return new bool?(DataReader.GetBoolean(i));
        }

        /// <summary>
        /// Gets the byte result with specified name.
        /// </summary>
        public override byte GetByte(string name)
        {
            return DataReader.GetByte(GetOrdinal(name));
        }

        /// <summary>
        /// Gets the nullable byte result with specified name.
        /// </summary>
        public override byte? GetNullableByte(string name)
        {
            var i = GetOrdinal(name);
            if (DataReader.IsDBNull(i))
                return new byte?();
            else
                return new byte?(DataReader.GetByte(i));
        }

        /// <summary>
        /// Gets the array of bytes with specified name.
        /// </summary>
        public override byte[] GetArrayOfByte(string name)
        {
            var i = GetOrdinal(name);
            var len = DataReader.GetBytes(i, 0, null, 0, int.MaxValue);
            byte[] buffer = new byte[len];
            DataReader.GetBytes(i, 0, buffer, 0, (int)len);
            return buffer;
        }

        /// <summary>
        /// Gets the char result with specified name.
        /// </summary>
        public override char GetChar(string name)
        {
            return DataReader.GetChar(GetOrdinal(name));
        }

        /// <summary>
        /// Gets the nullable char with specified name.
        /// </summary>
        public override char? GetNullableChar(string name)
        {
            var i = GetOrdinal(name);
            if (DataReader.IsDBNull(i))
                return new char?();
            else
                return new char?(DataReader.GetChar(i));
        }

        /// <summary>
        /// Gets the GetDateTime result with specified name.
        /// </summary>
        public override DateTime GetDateTime(string name)
        {
            return DataReader.GetDateTime(GetOrdinal(name));
        }

        /// <summary>
        /// Gets the nullable GetDateTime result with specified name.
        /// </summary>
        public override DateTime? GetNullableDateTime(string name)
        {
            var i = GetOrdinal(name);
            if (DataReader.IsDBNull(i))
                return new DateTime?();
            else
                return new DateTime?(DataReader.GetDateTime(i));
        }

        /// <summary>
        /// Gets the decimal result with specified name.
        /// </summary>
        public override decimal GetDecimal(string name)
        {
            return DataReader.GetDecimal(GetOrdinal(name));
        }

        /// <summary>
        /// Gets the nullable decimal result with specified name.
        /// </summary>
        public override decimal? GetNullableDecimal(string name)
        {
            var i = GetOrdinal(name);
            if (DataReader.IsDBNull(i))
                return new decimal?();
            else
                return new decimal?(DataReader.GetDecimal(i));
        }

        /// <summary>
        /// Gets the double result with specified name.
        /// </summary>
        public override double GetDouble(string name)
        {
            return DataReader.GetDouble(GetOrdinal(name));
        }

        /// <summary>
        /// Gets the nullable double result with specified name.
        /// </summary>
        public override double? GetNullableDouble(string name)
        {
            var i = GetOrdinal(name);
            if (DataReader.IsDBNull(i))
                return new double?();
            else
                return new double?(DataReader.GetDouble(i));
        }

        /// <summary>
        /// Gets the single result with specified name.
        /// </summary>
        public override float GetSingle(string name)
        {
            return DataReader.GetFloat(GetOrdinal(name));
        }

        /// <summary>
        /// Gets the nullable single result with specified name.
        /// </summary>
        public override float? GetNullableSingle(string name)
        {
            var i = GetOrdinal(name);
            if (DataReader.IsDBNull(i))
                return new float?();
            else
                return new float?(DataReader.GetFloat(i));
        }

        /// <summary>
        /// Gets the GetGuid result with specified name.
        /// </summary>
        public override Guid GetGuid(string name)
        {
            return DataReader.GetGuid(GetOrdinal(name));
        }

        /// <summary>
        /// Gets the nullable guid result with specified name.
        /// </summary>
        public override Guid? GetNullableGuid(string name)
        {
            var i = GetOrdinal(name);
            if (DataReader.IsDBNull(i))
                return new Guid?();
            else
                return new Guid?(DataReader.GetGuid(i));
        }

        /// <summary>
        /// Gets the Int32 result with specified name.
        /// </summary>
        public override int GetInt32(string name)
        {
            return DataReader.GetInt32(GetOrdinal(name));
            //return (int)DataReader.GetValue(GetOrdinal(name));
        }

        /// <summary>
        /// Gets the Nullable{Int32} result with specified name.
        /// </summary>
        public override int? GetNullableInt32(string name)
        {
            var i = GetOrdinal(name);
            if (DataReader.IsDBNull(i))
                return new int?();
            else
                return new int?(DataReader.GetInt32(i));
        }

        /// <summary>
        /// Gets the Int64 result with specified name.
        /// </summary>
        public override long GetInt64(string name)
        {
            return DataReader.GetInt64(GetOrdinal(name));
        }

        /// <summary>
        /// Gets the Nullable{Int64} result with specified name.
        /// </summary>
        public override long? GetNullableInt64(string name)
        {
            var i = GetOrdinal(name);
            if (DataReader.IsDBNull(i))
                return new long?();
            else
                return new long?(DataReader.GetInt64(i));
        }

        /// <summary>
        /// Gets the string result with specified name.
        /// </summary>
        public override string GetString(string name)
        {
            var ordinal = GetOrdinal(name);
            var str = DataReader.GetValue(ordinal);
            return str as String;
        }

        /// <summary>
        /// Gets the result with specified name.
        /// </summary>
        public override object GetValue(string name)
        {
            var result = DataReader.GetValue(GetOrdinal(name));
            if (result == DBNull.Value)
                return null;
            return result;
        }

        /// <summary>
        /// Determines whether the current instance contains result with the specified name.
        /// </summary>
        public override bool ContainsName(string name)
        {
            for (int i = 0; i < DataReader.FieldCount; i++)
            {
                if (StringComparer.OrdinalIgnoreCase.Equals(name, DataReader.GetName(i)))
                    return true;
            }

            return false;
        }

        #endregion

        private int GetOrdinal(string name)
        {
            return FieldIndex[name];
        }
    }

    /// <summary>
    /// Allows to obtain instances of <see cref="ReaderEnumerator"/> for specified 
    /// <see cref="IDataReader"/>.
    /// </summary>
    public sealed class ReadResult : IEnumerable<TypedRecord>
    {
        private readonly Func<DataCommandContainer> _ExecuteReaderFunc;

        /// <summary>
        /// Initializes a new instance of the <see cref="ReadResult"/> class.
        /// </summary>
        /// <param name="executeReaderFunc">The create command func.</param>
        public ReadResult(Func<DataCommandContainer> executeReaderFunc)
        {
            Debug.Assert(executeReaderFunc != null, "executeReaderFunc != null");
            _ExecuteReaderFunc = executeReaderFunc;
        }

        /// <summary>
        /// Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
        /// </returns>
        public IEnumerator<TypedRecord> GetEnumerator()
        {
            return new ReaderEnumerator(_ExecuteReaderFunc);
        }

        /// <summary>
        /// Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
        /// </returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}